﻿using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Force.Crc32;
using Google.Protobuf;
using KuiHuaBaoDian.Services.Net;
using UnityEngine.Assertions;

namespace Runtime.Net {

    public static class DFR_NetUtility {

        private readonly struct Text : IText {

            public string Value { get; init; }

            public Text(string value) { Value = value; }

            public override readonly string ToString() => Value;

            public override readonly bool Equals(object obj) {
                if (obj is Text other) {
                    return other.Value == Value;
                } else if (obj is IText other2) {
                    return other2.ToString() == Value;
                }
                return false;
            }

            public override readonly int GetHashCode() {
                return Value?.GetHashCode() ?? 0;
            }

            public static bool operator ==(Text left, Text right) => left.Equals(right);
            public static bool operator ==(Text left, IText right) => left.Equals(right);
            public static bool operator ==(IText left, Text right) => right.Equals(left);

            public static bool operator !=(Text left, Text right) => !left.Equals(right);
            public static bool operator !=(Text left, IText right) => !left.Equals(right);
            public static bool operator !=(IText left, Text right) => !right.Equals(left);
        }

        public static readonly NetPacketID<uint> INVALID_PACKET_ID = new();

        private const int PACKET_LENGTH_SIZE = 4;
        private const int PACKET_ID_SIZE = 4;
        private const int PACKET_HEAD_SIZE = PACKET_LENGTH_SIZE + PACKET_ID_SIZE;

        private static readonly IDictionary<NetPacketID<uint>, Type> m_MessageTypeDictionary = new Dictionary<NetPacketID<uint>, Type>();
        private static readonly IDictionary<Type, NetPacketID<uint>> m_PacketIDDictionary = new Dictionary<Type, NetPacketID<uint>>();

        public static void MapPacketID() {
            var assembly = typeof(ErrorCode).Assembly;
            var types = assembly.GetTypes();
            var messageType = typeof(IMessage);
            foreach (var type in types) {
                if (messageType.IsAssignableFrom(type)) {
                    var name = type.Name;
                    var bytes = Encoding.UTF8.GetBytes(name);
                    var id = Crc32Algorithm.Compute(bytes);
                    var packetID = new NetPacketID<uint>(id);
                    m_MessageTypeDictionary.Add(packetID, type);
                    m_PacketIDDictionary.Add(type, packetID);
                }
            }
        }

        public static bool TryGetMessage<TMessage>(INetPacket packet, out TMessage result) where TMessage : IMessage {
            if (TryGetMessage(typeof(TMessage), packet, out var r)) {
                result = (TMessage)r;
                return true;
            } else {
                result = default;
                return false;
            }
        }
        public static bool TryGetMessage(Type messageType, INetPacket packet, out IMessage result) {
            Assert.IsNotNull(messageType);
            Assert.IsNotNull(packet);
            Assert.IsTrue(typeof(IMessage).IsAssignableFrom(messageType));

            var message = (IMessage)Activator.CreateInstance(messageType);
            if (packet.Body != null && packet.Body.Length > 0) {
                message.MergeFrom(packet.Body);
            }
            (message as ISupportInitialize)?.EndInit();
            result = message;
            return true;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static NetPacketID<uint> GetPacketID<TMessage>() where TMessage : IMessage => GetPacketID(typeof(TMessage));
        public static NetPacketID<uint> GetPacketID(Type messageType) {
            Assert.IsNotNull(messageType);
            Assert.IsTrue(typeof(IMessage).IsAssignableFrom(messageType));

            if (m_PacketIDDictionary.TryGetValue(messageType, out var packetID)) {
                return packetID;
            }
            return INVALID_PACKET_ID;
        }

        private static readonly IDictionary<Type, PropertyInfo> s_ErrorCodePropertyDictionary = new Dictionary<Type, PropertyInfo>();
        public static bool TryGetErrorCode<TResponse>(TResponse response, out ErrorCode errorCode) where TResponse : IMessage {
            errorCode = ErrorCode.SUCCESS;
            if (response == null) {
                return false;
            }
            var type = response.GetType();
            if (!s_ErrorCodePropertyDictionary.TryGetValue(type, out var propertyInfo)) {
                propertyInfo = type.GetProperty("ErrorCode");
                s_ErrorCodePropertyDictionary[type] = propertyInfo;
            }
            if (propertyInfo != null) {
                errorCode = (ErrorCode)propertyInfo.GetValue(response);
            }
            return propertyInfo != null;
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static IText GetPacketIDText(INetPacketID packetID) => GetPacketIDText(packetID.ToUInt());
        public static IText GetPacketIDText(NetPacketID<uint> packetID) {
            if (m_MessageTypeDictionary.TryGetValue(packetID, out var type)) {
                var idString = new Text { Value = type.Name };
                return idString;
            }
            return null;
        }

        public static IText GetPacketContentText(INetPacket packet) {
            if (m_MessageTypeDictionary.TryGetValue(packet.ID.ToUInt(), out var type)) {
                var message = (IMessage)Activator.CreateInstance(type);
                if (packet.Body != null && packet.Body.Length > 0) {
                    message.MergeFrom(packet.Body);
                }
                return new Text { Value = message.ToString() };
            }
            return null;
        }

        public static bool SplitPacketBytes(byte[] packetBytes, out INetPacketID packetID, out byte[] packetBody) {
            Assert.IsNotNull(packetBytes);

            packetID = default;

            var idBytes = new byte[PACKET_ID_SIZE];
            Array.Copy(packetBytes, PACKET_LENGTH_SIZE, idBytes, 0, PACKET_ID_SIZE);
            var id = BinaryPrimitives.ReadUInt32BigEndian(idBytes);
            if (id == 0) {
                packetBody = null;
                return false;
            }
            packetID = new NetPacketID<uint>(id);
            if (packetBytes.Length == PACKET_HEAD_SIZE) {
                packetBody = null;
            } else {
                packetBody = new byte[packetBytes.Length - PACKET_HEAD_SIZE];
                Array.Copy(packetBytes, PACKET_HEAD_SIZE, packetBody, 0, packetBody.Length);
            }
            return true;
        }

        public static byte[] BuildPacketBytes(INetPacketID packetID, byte[] packetBody) {
            Assert.IsNotNull(packetBody);
            var idBytes = new byte[PACKET_ID_SIZE];
            BinaryPrimitives.WriteUInt32BigEndian(idBytes, (uint)packetID.Value);
            var length = (uint)(PACKET_HEAD_SIZE + packetBody.Length);
            var lengthBytes = new byte[PACKET_LENGTH_SIZE];
            BinaryPrimitives.WriteUInt32BigEndian(lengthBytes, (uint)length);
            var packetBytes = new byte[length];
            Array.Copy(lengthBytes, 0, packetBytes, 0, lengthBytes.Length);
            Array.Copy(idBytes, 0, packetBytes, lengthBytes.Length, idBytes.Length);
            Array.Copy(packetBody, 0, packetBytes, lengthBytes.Length + idBytes.Length, packetBody.Length);
            return packetBytes;
        }
    }
}